﻿#include "Readline.h"
#include "Reader.h"
#include "Printer.h"
#include "Env.h"
#include "Util.h"
#include "StaticList.h"
#include "Exception.h"
#include "Validation.h"
#include "Core.h"

Lisp::ValPtr READ(const std::string &input);
Lisp::ValPtr EVAL(Lisp::ValPtr ast, Lisp::EnvPtr env);
std::string PRINT(Lisp::ValPtr val);
std::string Rep(const std::string &input, Lisp::EnvPtr env);

static Lisp::Readline s_readline;
static Lisp::EnvPtr s_repEnv(new Lisp::Env);

static Lisp::BuiltInFuncType::ApplyFunc Add, Sub, Mul, Div;

int main()
{
  s_repEnv->Set("+", Lisp::BuiltInVal("+", Add));
  s_repEnv->Set("-", Lisp::BuiltInVal("-", Sub));
  s_repEnv->Set("*", Lisp::BuiltInVal("*", Mul));
  s_repEnv->Set("/", Lisp::BuiltInVal("/", Div));

  const std::string prompt = "user> ";
  std::string input;
  while (s_readline.Get(prompt, input))
  {
    std::string out = "";
    try
    {
      out = Rep(input, s_repEnv);
    }
    catch (const Lisp::EmptyInputException &e)
    {
      continue;
    }
    catch (const Lisp::RuntimeException &e)
    {
      out = "Err: " + e.What();
    }
    std::cout << out << std::endl;
  }
  return 0;
}

Lisp::ValPtr READ(const std::string &input)
{
  return Lisp::ReadStr(input);
}

Lisp::ValPtr EVAL(Lisp::ValPtr ast, Lisp::EnvPtr env)
{
  return ast->Eval(env);
}

Lisp::ValPtr APPLY(Lisp::ValPtr op, Lisp::ListPtr args)
{
  const Lisp::ApplicableType *handler = DYNAMIC_CAST(Lisp::ApplicableType, op);
  LISP_CHECK(handler != NULL,
             "\"%s\" is not applicable", op->ToString(true).c_str());

  return handler->Apply(args);
}

std::string PRINT(Lisp::ValPtr val)
{
  return Lisp::PrintStr(val);
}

std::string Rep(const std::string &input, Lisp::EnvPtr env)
{
  std::string res = PRINT(EVAL(READ(input), env));
  return res;
}

Lisp::ValPtr Add(const std::string &name, Lisp::ListPtr args)
{
  ARG_COUNT_IS(2);
  ARG(0, Lisp::IntegerType, a);
  ARG(1, Lisp::IntegerType, b);
  return Lisp::IntegerVal(a->Value() + b->Value());
}

Lisp::ValPtr Sub(const std::string &name, Lisp::ListPtr args)
{
  ARG_COUNT_IS(2);
  ARG(0, Lisp::IntegerType, a);
  ARG(1, Lisp::IntegerType, b);
  return Lisp::IntegerVal(a->Value() - b->Value());
}

Lisp::ValPtr Mul(const std::string &name, Lisp::ListPtr args)
{
  ARG_COUNT_IS(2);
  ARG(0, Lisp::IntegerType, a);
  ARG(1, Lisp::IntegerType, b);
  return Lisp::IntegerVal(a->Value() * b->Value());
}

Lisp::ValPtr Div(const std::string &name, Lisp::ListPtr args)
{
  ARG_COUNT_IS(2);
  ARG(0, Lisp::IntegerType, a);
  ARG(1, Lisp::IntegerType, b);
  LISP_CHECK(b->Value() != 0, "Division by zero");
  return Lisp::IntegerVal(a->Value() / b->Value());
}
